/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.view;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

import be.destin.skos.analyze.Faceter;
import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.html.SkosHtmlManager;
import be.destin.util.Util;

/**
 * @author Christophe Dupriez 
 *
 */
public class FaceterView {

	public static final int checkboxWidth = 13;
	public static final int levelWidth = 12;
	public static final int schemeIconWidth = 16;
	
	public static final float excess = 0.2f;

	/** log4j category */
	private static Logger log = Logger.getLogger(FaceterView.class);

	private Faceter faceter; // Data to display
	private SkosHtmlManager skosHtmlManager;
	private HashSet<String> zoomedRoles = new HashSet<String>();
	private HashMap<String,SkosViewType> viewTypes = new HashMap<String,SkosViewType>();

	private static final int maxZoom = 2000; // Max number of lines in zooming for faceting

	public static FaceterView getInstance (SkosHtmlManager aSkosHtmlManager, Faceter aFaceter) {
		FaceterView faceterView = null;
		HttpServletRequest request = aSkosHtmlManager.getRequest();
		HttpSession session = request.getSession();
		if (session != null) {
			faceterView = (FaceterView) session.getAttribute("SKOSfaceterview");
		}
		if (faceterView == null) {
			faceterView = new FaceterView();
			if (session != null) {
				session.setAttribute("SKOSfaceterview", faceterView);
			}
		}
		faceterView.setFaceter(aFaceter);
		faceterView.setSkosHtmlManager(aSkosHtmlManager);
		return faceterView;
	}

	public Faceter getFaceter() {
		return faceter;
	}

	public void setFaceter(Faceter faceter) {
		this.faceter = faceter;
	}

	public SkosViewType getViewType(String aRole) {
		SkosViewType viewType = this.viewTypes.get(aRole);
		if (viewType == null) viewType = SkosViewType.FREQ;
		return viewType;
	}

	public void setViewType(String aRole, SkosViewType viewType) {
		this.viewTypes.put(aRole,viewType);
	}

	public String getViewTypes() {
		StringBuffer result = new StringBuffer();
		for (String aRole : this.viewTypes.keySet()) {
			SkosViewType viewType = this.viewTypes.get(aRole);
			result.append(aRole);
			result.append('=');
			result.append(viewType.toString());
			result.append(", ");
		}
		return result.toString();
	}

	public boolean isZoomed(String aRole) {
		return (zoomedRoles.contains(aRole));
	}

	public void setZoomed(String aRole, boolean zoomed) {
		if (zoomed) {
			zoomedRoles.add(aRole);
		} else {
			zoomedRoles.remove(aRole);
		}
	}

	public void setSingleZoomed(String aRole) {
		zoomedRoles = new HashSet<String>();
		if (aRole != null && !aRole.isEmpty()) {
			zoomedRoles.add(aRole);
		}
	}

	public SkosHtmlManager getSkosHtmlManager() {
		return skosHtmlManager;
	}

	public void setSkosHtmlManager(SkosHtmlManager skosHtmlManager) {
		this.skosHtmlManager = skosHtmlManager;
	}

	class ToHtml {
		int tickables;
		int remainingConcepts;
		int limit;
		String anApplication;
		String aRole;

		ToHtml(String application, String role) {
			anApplication = application;
			aRole = role;
			tickables = 0;
			remainingConcepts = faceter.getNbConcept(role);
			limit = (isZoomed(aRole) ? maxZoom : faceter.getMenuLimit()); 
		}
	}

	public void toHtmlOut(Writer out, String actionInQuery, String selected)
	{
		try {
			out.write("<table>");
			for (String aRole: skosHtmlManager.getSkosManager().getDefinedRolesList()) {
				if (faceter.getFreqMap().containsKey(aRole)) {
					if (faceter.getFreqMap(aRole).size() > 0) {
						ToHtml params = new ToHtml(skosHtmlManager.getApplication(), aRole);
						out.write("<tr><th>"+faceter.getNbDoc()
								+"</th><th align=\"left\">"
								+skosHtmlManager.toHtmlRole(params.anApplication, aRole, null)
								+" "//+" <tt>"+aRole+":</tt> "
								+(faceter.getSampling()>1?"<i>":"")
								+faceter.getNbConcept(aRole)
								+(faceter.getSampling()>1?"</i>":"")
								+" </td></tr>");
						switch (getViewType(aRole)) {
						case FREQ:
							out.write(freqMapToHtml(params,
									actionInQuery, selected ));
							break;
						case ALFA:
							out.write(freqMapToAlfaToHtml(params,
									actionInQuery, selected ));
							break;
						case TREE:
							out.write(freqMapToTreeToHtml(params,
									actionInQuery, selected ));
							break;

						}
						if (params.tickables > 0 || faceter.getNbConcept(aRole) > 1) {
							out.write("<tr><th></th><td>");
							if (params.tickables > 0) {
								out.write("<a href=\"javascript:faceterFilter()\"><img");
								out.write(skosHtmlManager.askosiIcon("filter"));
								out.write(" class=\"imagelinkbutton\"\"/></a>&nbsp;");
							}
							if (faceter.getNbConcept(aRole) > 1) {
								out.write(SkosViewType.toHtmlButtons(skosHtmlManager,"faceterSort",getViewType(aRole),faceter.getMaxDepth(aRole),aRole));
								if (isZoomed(aRole)) {
									out.write("<a href=\"javascript:faceterDetails(\'");
									out.write(params.anApplication);
									out.write("\',\'NOZOOM\',");
									out.write(Integer.toString(faceter.getMenuLimit()));
									out.write(")\"><img");
									out.write(skosHtmlManager.askosiIcon("view_unzoom"));
									out.write(" class=\"imagelinkbutton\"/></a>");
								} else if (params.remainingConcepts > 0) {
									out.write("<a href=\"javascript:faceterDetails(\'");
									out.write(params.anApplication);
									out.write("\',\'");
									out.write(aRole);
									out.write("\',");
									out.write(Integer.toString(faceter.getMenuLimit()));
									out.write(")\" class=\"imagelinkbutton\">");
									if (faceter.getSampling() > 1) out.write("&plusmn;");
									else out.write('+');
									out.write(Integer.toString(params.remainingConcepts));
									out.write(" ...</a>");
								}
							}
							out.write("</td></tr>");
						}
					}
				}
			}
			out.write("</table>");
		} catch (IOException e) {
			// TODO Auto-generated catch block	
			e.printStackTrace();
		}
	}

	private ArrayList<String> getTopFreq(ToHtml params) {
		ArrayList<String> result = new ArrayList<String>();
		TreeMap<Integer,String[]> thisMap = faceter.getFreqMap(params.aRole);
		NavigableSet<Integer> freqs = thisMap.descendingKeySet();
		int remains = params.limit;
		for (Integer aFreq : freqs) {
			if (remains <= 0 && params.remainingConcepts > 1) break;
			int internLimit = params.limit;
			for (String anAbout: thisMap.get(aFreq)) {
					remains--;
					internLimit--;
					if (internLimit < 0 && params.remainingConcepts > 1) break;
					params.remainingConcepts --;
				result.add(anAbout);
			}
		}
		return result;
	}
	
	public String freqMapToHtml(ToHtml params,
			String inQueryAction, String selected) {
		String userLanguage = skosHtmlManager.getLanguage();
		TreeMap<Integer,String[]> thisMap = faceter.getFreqMap(params.aRole);
		NavigableSet<Integer> freqs = thisMap.descendingKeySet();
		StringBuffer result = new StringBuffer();
		int remains = params.limit;
		for (Integer aFreq : freqs) {
			if (remains <= 0 && params.remainingConcepts > 1) break;
			int internLimit = params.limit;
			int nbRow = thisMap.get(aFreq).length;
			if (nbRow > internLimit) {
				if (internLimit == params.remainingConcepts-1) nbRow = params.remainingConcepts;
				else nbRow = internLimit;
			}
			result.append("<tr><th rowspan=\""+nbRow+"\">");
			int freq = aFreq;
			boolean pasOp = freq >= faceter.getNbDoc();
			if (pasOp) {
				freq = faceter.getNbDoc();
			} else /*if (sampling <= 1)*/{
				result.append(freq);
			} /* else {
					String freqShown = Integer.toString(freq);
					if (freq >= 10) {
						result.append(freqShown.charAt(0));
						freqShown = freqShown.substring(1);
					}
					result.append("<small>");
					result.append(freqShown);
					result.append("</small>");
				}*/
			result.append("</th>");
			boolean first = true;
			TreeMap<String,Concept> sorter = new TreeMap<String,Concept>(); 
			for (String anAbout: thisMap.get(aFreq)) {
				Concept aConcept = skosHtmlManager.getSkosManager().getScheme_Concept(anAbout);
				if (aConcept != null) {
					String key = aConcept.getSortLabel(userLanguage);
					sorter.put(aConcept.getInScheme().getAbout()+' '+key, aConcept);
				}
			}
			for (Concept aConcept: sorter.values()) {
				remains--;
				internLimit--;
				if (internLimit < 0 && params.remainingConcepts > 1) break;
				params.remainingConcepts --;
				if (!first) {
					result.append("<tr>");
				} else {
					first = false;
				}
				String aName = aConcept.getPrefLabel(userLanguage, false);
				if (aName == null || aName.length() <= 0) {
					aName = aConcept.getAbout();
				} else {
					aName = Util.escapeHTMLEntities(aName);
				}
				String conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getIconAdapted(skosHtmlManager),aName,false);
				if (conceptIcon == null || conceptIcon.isEmpty()) {
					conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getInScheme().getIconAdapted(skosHtmlManager),aConcept.getInScheme().getAbout(),false);
				}
				int indent = checkboxWidth + (conceptIcon == null || conceptIcon.isEmpty()? 0 : schemeIconWidth);
				result.append("<td style=\"padding-left:"+indent+"px; text-indent:-"+indent+"px;\">");
				String fullHandle = aConcept.getScheme_About();
				int countConcept = aConcept.getCount(params.anApplication,params.aRole);
				if (!pasOp) {
					String keyEntry =':'+params.anApplication+'.'+params.aRole+'.'+fullHandle+':';
					String suffix = "correl";
					float ratio = -9999f;
					int totalDoc = faceter.getNbDocInBase();
					if (totalDoc > 0) {
						ratio = ((float)(countConcept*faceter.getNbDoc())) / (float)totalDoc;
						if (((float)freq-ratio) > (ratio*excess)) suffix="correlhot";
						else if ((ratio-(float)freq) > (ratio*excess)) suffix="correlcold";
					}
					params.tickables++;
					result.append("<span class=\"");
					result.append(suffix);
					result.append("\"><input type=\"checkbox\" name=\"");
					result.append(keyEntry);
					result.append("\"");
					if (selected != null) {
						if (selected.indexOf(keyEntry) >= 0) {
							result.append(" checked");
						}
					}
					result.append(" class=\"facetcheckbox\" onchange=\"javascript:facetCheckboxChange()\"/></span>");
				} else {
					//<div style="display:inline; border-style:inset; font-size:11px; color:black" id="facetfiltercount"></div>
					result.append("<img");
					result.append(skosHtmlManager.askosiIcon("filler"));
					result.append(" class=\"facetfiller\"/>");
				}
				result.append("<a href=\"#\" onclick=\"javascript:return faceterOperations(event,\'");
				result.append(params.anApplication);
				result.append("\',\'");
				result.append(params.aRole);
				result.append("\',\'");
				result.append(fullHandle);
				result.append("\',");
				result.append(freq);
				result.append(",");
				result.append(countConcept);
				result.append(")\" class=\"facetchoice\">");
				result.append(conceptIcon);
				result.append(aName);
				result.append("</a>");
				String query = faceter.getQuery(); 
				if (query != null) {
					int posConcept = query.indexOf(fullHandle);
					if (posConcept >= 0) {
						if (inQueryAction != null && !inQueryAction.isEmpty()) {
							if (posConcept == 0 || !Character.isJavaIdentifierPart(query.charAt(posConcept-1))) {
								int endConcept = posConcept+fullHandle.length();
								if (endConcept >= query.length() || !Character.isJavaIdentifierPart(query.charAt(endConcept))) {
									String toDo = Util.replaceAllNoRegExp(inQueryAction, "[about]", fullHandle);
									toDo = Util.replaceAllNoRegExp(toDo, "[begin]", ""+posConcept);
									toDo = Util.replaceAllNoRegExp(toDo, "[end]", ""+endConcept);
									result.append(toDo);
								}
							}
						}
					}
				}
				result.append("</td></tr>");
			}
			if (internLimit <= 0 && params.remainingConcepts > 1) {
				break;
			}
		}
		return result.toString();	
	}

	public String freqMapToAlfaToHtml(ToHtml params,
			String inQueryAction, String selected) {
		String userLanguage = skosHtmlManager.getLanguage();
		ArrayList<String> topsAbout = getTopFreq(params);
		StringBuffer result = new StringBuffer();
		TreeMap<String,Concept> sorter = new TreeMap<String,Concept>();
		for (String anAbout : topsAbout) {
			Concept aConcept = skosHtmlManager.getSkosManager().getScheme_Concept(anAbout);
			if (aConcept != null) {
				String key = aConcept.getSortLabel(userLanguage);
				sorter.put(aConcept.getInScheme().getAbout()+' '+key, aConcept);
			}
		}
		HashMap<String,Integer> mapConcept2Freq = faceter.getConceptMap(skosHtmlManager.getSkosManager(), params.aRole);
		for (Concept aConcept : sorter.values()) {
			result.append("<tr><th>");
			int freq = mapConcept2Freq.get(aConcept.getScheme_About());
			boolean pasOp = freq >= faceter.getNbDoc();
			if (pasOp) {
				freq = faceter.getNbDoc();
			} else /*if (sampling <= 1)*/{
				result.append(freq);
			} /* else {
					String freqShown = Integer.toString(freq);
					if (freq >= 10) {
						result.append(freqShown.charAt(0));
						freqShown = freqShown.substring(1);
					}
					result.append("<small>");
					result.append(freqShown);
					result.append("</small>");
				}*/
			String aName = aConcept.getPrefLabel(userLanguage, false);
			if (aName == null || aName.length() <= 0) {
				aName = aConcept.getAbout();
			} else {
				aName = Util.escapeHTMLEntities(aName);
			}
			String conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getIconAdapted(skosHtmlManager),aName,false);
			if (conceptIcon == null || conceptIcon.isEmpty()) {
				conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getInScheme().getIconAdapted(skosHtmlManager),aConcept.getInScheme().getAbout(),false);
			}
			String fullHandle = aConcept.getScheme_About();
			int indent = checkboxWidth + (conceptIcon == null || conceptIcon.isEmpty()? 0 : schemeIconWidth);
			result.append("</th><td style=\"padding-left:"+indent+"px; text-indent:-"+indent+"px;\">");
			int countConcept = aConcept.getCount(params.anApplication,params.aRole);
			if (!pasOp) {
				params.tickables++;
				String keyEntry =':'+params.anApplication+'.'+params.aRole+'.'+fullHandle+':';
				String suffix = "correl";
				float ratio = -9999f;
				int totalDoc = faceter.getNbDocInBase();
				if (totalDoc > 0) {
					ratio = ((float)(countConcept*faceter.getNbDoc())) / (float)totalDoc;
					if (((float)freq-ratio) > (ratio*excess)) suffix="correlhot";
					else if ((ratio-(float)freq) > (ratio*excess)) suffix="correlcold";
				}
				params.tickables++;
				result.append("<span class=\"");
				result.append(suffix);
				result.append("\"><input type=\"checkbox\" name=\"");
				result.append("<input type=\"checkbox\" name=\"");
				result.append(keyEntry);
				result.append("\"");
				if (selected != null) {
					if (selected.indexOf(keyEntry) >= 0) {
						result.append(" checked");
					}
				}
				result.append(" class=\"facetcheckbox"+suffix+"\" onchange=\"javascript:facetCheckboxChange()\"/></span>");
				//result.append("f="+freq+",Eq="+faceter.getNbDoc()+",Con="+countConcept+",Base="+totalDoc);
			} else {
				//<div style="display:inline; border-style:inset; font-size:11px; color:black" id="facetfiltercount"></div>
				result.append("<img");
				result.append(skosHtmlManager.askosiIcon("filler"));
				result.append(" class=\"facetfiller\"/>");
			}
			result.append("<a href=\"#\" onclick=\"javascript:return faceterOperations(event,\'");
			result.append(params.anApplication);
			result.append("\',\'");
			result.append(params.aRole);
			result.append("\',\'");
			result.append(fullHandle);
			result.append("\',");
			result.append(freq);
			result.append(",");
			result.append(countConcept);
			result.append(")\" class=\"facetchoice\">");

			result.append(conceptIcon);
			result.append(aName);
			result.append("</a>");
			String query = faceter.getQuery(); 
			if (query != null) {
				int posConcept = query.indexOf(fullHandle);
				if (posConcept >= 0) {
					if (inQueryAction != null && !inQueryAction.isEmpty()) {
						if (posConcept == 0 || !Character.isJavaIdentifierPart(query.charAt(posConcept-1))) {
							int endConcept = posConcept+fullHandle.length();
							if (endConcept >= query.length() || !Character.isJavaIdentifierPart(query.charAt(endConcept))) {
								String toDo = Util.replaceAllNoRegExp(inQueryAction, "[about]", fullHandle);
								toDo = Util.replaceAllNoRegExp(toDo, "[begin]", ""+posConcept);
								toDo = Util.replaceAllNoRegExp(toDo, "[end]", ""+endConcept);
								result.append(toDo);
							}
						}
					}
				}
			}
			result.append("</td></tr>");
		}
		return result.toString();	
	}

	private String treeToHtml (ToHtml params, Concept aConcept, int level, HashSet<String> known, HashSet<String> broaders, HashSet<String> noRepeat, String inQueryAction, String selected) {
		StringBuffer result = new StringBuffer();
		String userLanguage = skosHtmlManager.getLanguage();
		result.append("<tr><th>");
		String fullHandle = aConcept.getScheme_About();
		int freq = 0;
		boolean pasOp = true;
		boolean clickable = known.contains(fullHandle);
		boolean noMore = noRepeat.contains(fullHandle);
		if (!noMore) {
			noRepeat.add(fullHandle);
		}
		if (clickable) {
			HashMap<String,Integer> mapConcept2Freq = faceter.getConceptMap(skosHtmlManager.getSkosManager(), params.aRole);
			freq = mapConcept2Freq.get(fullHandle);
			pasOp = freq >= faceter.getNbDoc();
		}
		if (pasOp) {
			freq = faceter.getNbDoc();
		} else /*if (sampling <= 1)*/{
			result.append(freq);
		} /* else {
				String freqShown = Integer.toString(freq);
				if (freq >= 10) {
					result.append(freqShown.charAt(0));
					freqShown = freqShown.substring(1);
				}
				result.append("<small>");
				result.append(freqShown);
				result.append("</small>");
			}*/
		String aName = aConcept.getPrefLabel(userLanguage, false);
		if (aName == null || aName.length() <= 0) {
			aName = aConcept.getAbout();
		} else {
			aName = Util.escapeHTMLEntities(aName);
		}
		String conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getIconAdapted(skosHtmlManager),aName,false);
		if (conceptIcon == null || conceptIcon.isEmpty()) {
			conceptIcon = skosHtmlManager.toHtmlImage(aConcept.getInScheme().getIconAdapted(skosHtmlManager),aConcept.getInScheme().getAbout(),false);
		}
		int countConcept = aConcept.getCount(params.anApplication,params.aRole);
		int indent = (levelWidth * level) + checkboxWidth + (conceptIcon == null || conceptIcon.isEmpty()? 0 : schemeIconWidth);
		result.append("</th><td style=\"padding-left:"+indent+"px; text-indent:-"+indent+"px;\">");
		if (!pasOp) {
			params.tickables++;
			String keyEntry =':'+params.anApplication+'.'+params.aRole+'.'+fullHandle+':';
			String suffix = "correl";
			float ratio = -9999f;
			int totalDoc = faceter.getNbDocInBase();
			if (totalDoc > 0) {
				ratio = ((float)(countConcept*faceter.getNbDoc())) / (float)totalDoc;
				if (((float)freq-ratio) > (ratio*excess)) suffix="correlhot";
				else if ((ratio-(float)freq) > (ratio*excess)) suffix="correlcold";
			}
			params.tickables++;
			result.append("<span class=\"");
			result.append(suffix);
			result.append("\"><input type=\"checkbox\" name=\"");
			result.append(keyEntry);
			result.append("\"");
			if (selected != null) {
				if (selected.indexOf(keyEntry) >= 0) {
					result.append(" checked");
				}
			}
			result.append(" class=\"facetcheckbox\" onchange=\"javascript:facetCheckboxChange()\"/>");
		} else {
			//<div style="display:inline; border-style:inset; font-size:11px; color:black" id="facetfiltercount"></div>
			result.append("<img");
			result.append(skosHtmlManager.askosiIcon("filler"));
			result.append(" class=\"facetfiller\"/>");
		}

		for (int i = 0; i < level; i++) {
			result.append("<img");
			result.append(skosHtmlManager.askosiIcon("indent"));
			result.append(" class=\"facetfiller\"/>");
		}
		if (clickable) {
			result.append("<a href=\"#\" onclick=\"javascript:return faceterOperations(event,\'");
			result.append(params.anApplication);
			result.append("\',\'");
			result.append(params.aRole);
			result.append("\',\'");
			result.append(fullHandle);
			result.append("\',");
			result.append(freq);
			result.append(",");
			result.append(countConcept);
			result.append(")\" class=\"facetchoice\">");
		}
		result.append(conceptIcon);
		result.append(aName);
		if (noMore) {
			result.append(SkosView.repeatedConcept);
		}
		if (clickable) {
			result.append("</a>");
		}
		String query = faceter.getQuery(); 
		if (query != null) {
			int posConcept = query.indexOf(fullHandle);
			if (posConcept >= 0) {
				if (inQueryAction != null && !inQueryAction.isEmpty()) {
					if (posConcept == 0 || !Character.isJavaIdentifierPart(query.charAt(posConcept-1))) {
						int endConcept = posConcept+fullHandle.length();
						if (endConcept >= query.length() || !Character.isJavaIdentifierPart(query.charAt(endConcept))) {
							String toDo = Util.replaceAllNoRegExp(inQueryAction, "[about]", fullHandle);
							toDo = Util.replaceAllNoRegExp(toDo, "[begin]", ""+posConcept);
							toDo = Util.replaceAllNoRegExp(toDo, "[end]", ""+endConcept);
							result.append(toDo);
						}
					}
				}
			}
		}
		result.append("</td></tr>");

		if (!noMore) {
			TreeMap<String,Concept> sorter = new TreeMap<String,Concept>();
			ConceptScheme currScheme = aConcept.getInScheme();
			for (String deeper : aConcept.getAboutNarrower()) {
				Concept aConceptDeeper = currScheme.getConcept(deeper);
				if (aConceptDeeper != null) {
					String fullDeeper = aConceptDeeper.getScheme_About();
					if (broaders.contains(fullDeeper) || known.contains(fullDeeper)) {
						String key = aConceptDeeper.getSortLabel(userLanguage);
						sorter.put(deeper+' '+key, aConceptDeeper);
					}
				}
			}
			for (Concept aNarrowConcept : sorter.values()) {
				result.append(treeToHtml(params, aNarrowConcept, level+1, known, broaders, noRepeat, inQueryAction, selected));
			}
		}
		return result.toString();
	}

	public String freqMapToTreeToHtml(ToHtml params,
			String inQueryAction, String selected) {
		String userLanguage = skosHtmlManager.getLanguage();
		StringBuffer result = new StringBuffer();
		HashSet<String> broaders = new HashSet<String>();
		HashSet<String> tops = new HashSet<String>();
		HashSet<String> known = new HashSet<String>();
		ArrayList<String> topsAbout = getTopFreq(params);
		for (String anAbout : topsAbout) {
			Concept aConcept = skosHtmlManager.getSkosManager().getScheme_Concept(anAbout);
			if (aConcept != null) {
				List<String> broadersThis = aConcept.getScheme_AboutBroaderTransitive();
				if (broadersThis != null && !broadersThis.isEmpty()) {
					broaders.addAll(broadersThis);
				}
				List<String> topThis = aConcept.getScheme_AboutTopConcepts();
				if (topThis != null && !topThis.isEmpty()) {
					tops.addAll(topThis);
				}
				known.add(anAbout);
			}
		}
		TreeMap<String,Concept> sorter = new TreeMap<String,Concept>();
		for (String aboutTop : tops) {
			Concept aConcept = skosHtmlManager.getSkosManager().getScheme_Concept(aboutTop);
			if (aConcept != null) {
				String key = aConcept.getSortLabel(userLanguage);
				sorter.put(aConcept.getInScheme().getAbout()+' '+key, aConcept);
			}
		}
		HashSet<String> noRepeat = new HashSet<String>();
		for (Concept aConcept : sorter.values()) {
			result.append(treeToHtml(params, aConcept, 0, known, broaders, noRepeat, inQueryAction, selected));
		}
		return result.toString();	
	}
}
